#include "Common.h"
#include "GL/glew.h"
#include "OpenGLRenderer.h"
#include "Utils/Util.h"
#include "App/AppBase.h"
#include "Graphics/OpenGL/OpenGLFont.h"
#include "OpenGLTexture.h"
#include "OpenGLRenderTarget.h"

#include <stdarg.h>

using namespace RayFrame;
using namespace glm;


#if defined WIN32
#include "OpenGLRendererWin32.h"
#elif defined LINUX
#include "OpenGLRendererLinux.h"
#endif

IMPL_SINGLETON(OpenGLRenderer)

GLenum usages[] = {
  GL_STATIC_DRAW,
  GL_DYNAMIC_DRAW,
  GL_STREAM_DRAW,
};

extern AppBase* g_App;

struct RayFrame::Attrib{
  int stream;
  int size;
  AttributeFormat format;
  int offset;
};

struct DLL_DECLARE RayFrame::VertexBuffer{
  GLuint vboVB;
  GLsizei size;
};

struct DLL_DECLARE RayFrame::VertexFormat{
  Attrib generic[MAX_GENERIC];
  Attrib texCoord[MAX_TEXCOORD];
  Attrib vertex;
  Attrib normal;

  int vertexSize[MAX_VERTEXSTREAM];
  int maxGeneric;
  int maxTexcoord;
};

struct DLL_DECLARE RayFrame::IndexBuffer{
  GLuint vboIB;

  unsigned nIndices;
  unsigned indexSize;
};

const GLenum glPrim[] =
{
  GL_TRIANGLES,
  GL_TRIANGLE_FAN,
  GL_TRIANGLE_STRIP,
  GL_QUADS,
  GL_LINES,
  GL_LINE_STRIP,
  GL_LINE_LOOP,
  GL_POINTS,
};

OpenGLRenderer::OpenGLRenderer()
{
}

bool OpenGLRenderer::Init()
{
  GLenum error = glewInit();
  if( error != GLEW_OK )
  {
    ErrorMsg("Error while setting up glew.");
    return false;
  }

  int vsync = 0;
  Configure::InstanceRef().GetValueNum(VSYNC,vsync);
#ifdef _WIN32
  // do we need vertical sync?
  if(vsync)
    wglSwapIntervalEXT(1);
  else
    wglSwapIntervalEXT(0);
#endif

  glClearColor(m_clearColor.r, m_clearColor.g, m_clearColor.b, m_clearColor.a);
  glEnable(GL_DEPTH_TEST);
  glEnable(GL_CULL_FACE);
  glShadeModel(GL_SMOOTH);
  glDisable(GL_LIGHTING);
  glEnable(GL_MULTISAMPLE);

  STRING defaultFont;
  int defaultFontSize;
  Configure::InstanceRef().GetValue(DEFAULT_FONT, defaultFont);
  Configure::InstanceRef().GetValueNum(DEFAULT_FONT_SIZE, defaultFontSize);
  AddSystemFont("fontDefault", defaultFont, defaultFontSize);
  return true;
}

#ifdef WIN32
void OpenGLRenderer::SetPlatformParam(HDC hdc, HGLRC glContext)
{
  //m_hdc = hdc;
  //m_glContext = glContext;
}
#elif defined LINUX
// void OpenGLRenderer::SetPlatformParam(XF86VidModeModeInfo** modes, GLXContext glContext)
// {
//   dmodes = modes;
//   m_glContext = glContext;
// }
#endif

VertexFormatID OpenGLRenderer::AddVertexFormat(const FormatDesc *formatDesc, const unsigned nAttribs)
{
  VertexFormat vertexFormat;

  memset(&vertexFormat, 0, sizeof(vertexFormat));

  unsigned nGeneric = 0;
  unsigned nTexCoord = 0;

  for (unsigned i=0; i<nAttribs; i++)
  {
    // Generic attribute 0 aliases with gl_vertex
    if(formatDesc[i].type == TYPE_VERTEX){
      nGeneric = 1;
      break;
    }
  }

  for(unsigned i = 0; i< nAttribs; i++){
    int stream = formatDesc[i].stream;

    switch(formatDesc[i].type)
    {
      case TYPE_GENERIC:
      case TYPE_TANGENT:
      case TYPE_BINORMAL:
        vertexFormat.generic[nGeneric].stream = stream;
        vertexFormat.generic[nGeneric].size   = formatDesc[i].size;
        vertexFormat.generic[nGeneric].offset = vertexFormat.vertexSize[stream];
        vertexFormat.generic[nGeneric].format = formatDesc[i].format;
        nGeneric++;
        break;
      case TYPE_VERTEX:
        vertexFormat.vertex.stream  = stream;
        vertexFormat.vertex.size    = formatDesc[i].size;
        vertexFormat.vertex.offset  = vertexFormat.vertexSize[stream];
        vertexFormat.vertex.format  = formatDesc[i].format;
        break;
      case TYPE_NORMAL:
        vertexFormat.normal.stream = stream;
        vertexFormat.normal.size   = formatDesc[i].size;
        vertexFormat.normal.offset = vertexFormat.vertexSize[stream];
        vertexFormat.normal.format = formatDesc[i].format;
        break;
      case TYPE_TEXCOORD:
        vertexFormat.texCoord[nTexCoord].stream = stream;
        vertexFormat.texCoord[nTexCoord].size   = formatDesc[i].size;
        vertexFormat.texCoord[nTexCoord].offset = vertexFormat.vertexSize[stream];
        vertexFormat.texCoord[nTexCoord].format = formatDesc[i].format;
        nTexCoord++;
        break;
    }

    vertexFormat.vertexSize[stream] += formatDesc[i].size * GetFormatSize(formatDesc[i].format);
  }

  vertexFormat.maxGeneric = nGeneric;
  vertexFormat.maxTexcoord = nTexCoord;

  return vertexFormats.Add(vertexFormat);
}

VertexBufferID OpenGLRenderer::AddVertexBuffer(const long size, const BufferAccess bufferAccess, const void *data)
{
  VertexBuffer vb;

  vb.size = size;

  glGenBuffersARB(1, &vb.vboVB);
  glBindBufferARB(GL_ARRAY_BUFFER_ARB, vb.vboVB);
  glBufferDataARB(GL_ARRAY_BUFFER_ARB, size, data, usages[bufferAccess]);
  glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);

  return vertexBuffers.Add(vb);
}


IndexBufferID OpenGLRenderer::AddIndexBuffer(const unsigned nIndices, const unsigned indexSize, const BufferAccess bufferAccess, const void *data)
{
  IndexBuffer ib;

  ib.nIndices = nIndices;
  ib.indexSize = indexSize;

  unsigned size = nIndices * indexSize;
  glGenBuffersARB(1, &ib.vboIB);
  glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, ib.vboIB);
  glBufferDataARB(GL_ELEMENT_ARRAY_BUFFER_ARB, size, data, usages[bufferAccess]);
  glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0);

  return indexBuffers.Add(ib);
}

void OpenGLRenderer::DrawElements(const Primitives prim, const int firstIndex, const int nIndices, const int firstVertex, const int nVertices)
{
  unsigned indexSize = indexBuffers[currentIndexBuffer].indexSize;

  glDrawElements(glPrim[prim], nIndices,
    indexSize == 2? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, BUFFER_OFFSET(0));
}


void OpenGLRenderer::ChangeVertexFormat(const VertexFormatID vertexFormat){
  if (vertexFormat != currentVertexFormat){
    static VertexFormat zero;
    VertexFormat *curr = &zero, *sel = &zero;

    if (currentVertexFormat != VF_NONE) curr = &vertexFormats[currentVertexFormat];
    if (vertexFormat != VF_NONE) sel = &vertexFormats[vertexFormat];

    // Change array enables as needed
    if ( sel->vertex.size && !curr->vertex.size)
      glEnableClientState (GL_VERTEX_ARRAY);
    if (!sel->vertex.size &&  curr->vertex.size)
      glDisableClientState(GL_VERTEX_ARRAY);
    if ( sel->normal.size && !curr->normal.size)
      glEnableClientState (GL_NORMAL_ARRAY);
    if (!sel->normal.size &&  curr->normal.size)
      glDisableClientState(GL_NORMAL_ARRAY);

    for (int i = 0; i < MAX_GENERIC; i++){
      if ( sel->generic[i].size && !curr->generic[i].size)
        glEnableVertexAttribArrayARB(i);
      if (!sel->generic[i].size &&  curr->generic[i].size)
        glDisableVertexAttribArrayARB(i);
    }

    for (int i = 0; i < MAX_TEXCOORD; i++){
      if ((sel->texCoord[i].size > 0) ^ (curr->texCoord[i].size > 0)){
        glClientActiveTexture(GL_TEXTURE0 + i);
        if (sel->texCoord[i].size > 0){
          glEnableClientState(GL_TEXTURE_COORD_ARRAY);
        } else {
          glDisableClientState(GL_TEXTURE_COORD_ARRAY);
        }
      }
    }

    currentVertexFormat = vertexFormat;
  }
}

void OpenGLRenderer::ChangeVertexBuffer(const int stream, const VertexBufferID vertexBuffer, const int* offset){
  const GLsizei glTypes[] = {
    GL_FLOAT,
    0, // No half float support
    GL_UNSIGNED_BYTE,
  };

  GLuint vbo = 0;
  if (vertexBuffer != VB_NONE) vbo = vertexBuffers[vertexBuffer].vboVB;

  if (vbo != currentVBO){
    glBindBufferARB(GL_ARRAY_BUFFER_ARB, currentVBO = vbo);
  }

  if (vertexBuffer != currentVertexBuffers[stream] ||
    offset != currentOffsets[stream] ||
    currentVertexFormat != activeVertexFormat[stream]){
    if (currentVertexFormat != VF_NONE){
      char *base = (char *) offset;

      VertexFormat *cvf = &vertexFormats[currentVertexFormat];
      int vertexSize = cvf->vertexSize[stream];

      if (cvf->vertex.stream == stream && cvf->vertex.size){
        glVertexPointer(cvf->vertex.size, glTypes[cvf->vertex.format], vertexSize, base + cvf->vertex.offset);
      }

      if (cvf->normal.stream == stream && cvf->normal.size){
        glNormalPointer(glTypes[cvf->normal.format], vertexSize, base + cvf->normal.offset);
      }

      for (int i = 0; i < MAX_GENERIC; i++){
        if (cvf->generic[i].stream == stream && cvf->generic[i].size){
          glVertexAttribPointerARB(i, cvf->generic[i].size, glTypes[cvf->generic[i].format], GL_TRUE, vertexSize, base + cvf->generic[i].offset);
        }
      }

      for (int i = 0; i < MAX_TEXCOORD; i++){
        if (cvf->texCoord[i].stream == stream && cvf->texCoord[i].size){
          glClientActiveTexture(GL_TEXTURE0 + i);
          glTexCoordPointer(cvf->texCoord[i].size, glTypes[cvf->texCoord[i].format], vertexSize, base + cvf->texCoord[i].offset);
        }
      }
    }

    currentVertexBuffers[stream] = vertexBuffer;
    currentOffsets[stream] = offset;
    activeVertexFormat[stream] = currentVertexFormat;
  }
}

void OpenGLRenderer::ChangeIndexBuffer(const IndexBufferID indexBuffer){
  if (indexBuffer != currentIndexBuffer){
    if (indexBuffer == IB_NONE){
      glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0);
    } else {
      glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, indexBuffers[indexBuffer].vboIB);
    }

    currentIndexBuffer = indexBuffer;
  }
}

bool OpenGLRenderer::CheckError()
{
  bool result;
#ifdef _DEBUG
  result = true;

  GLenum errorID = glGetError();
  while(errorID != GL_NO_ERROR)
  {
    const GLubyte* msg = gluErrorString(errorID);
    printf("Error: %s\n", msg);
    ErrorMsg( STRING( (const char*)msg ) );
    result = false;
    errorID = glGetError();
  }
#endif
  return result;
}

void OpenGLRenderer::SwapBuffer()
{
  if(g_App && g_App->GetFrame())
    g_App->GetFrame()->SwapBuffer();
}

void OpenGLRenderer::Finish()
{
  glFinish();
}

void OpenGLRenderer::Flush()
{
  glFlush();
}

void OpenGLRenderer::Resize(int width, int height)
{
  g_App->GetCamera()->SetViewportSize( vec2(width, height) );
}

void OpenGLRenderer::RenderText(int x, int y, STRING message)
{
  m_defaultFont->PrintText(x, y, message);
}

void OpenGLRenderer::RenderText(int x, int y, const char *string, ...)
{
  char  text[1024];                    // Holds Our String
  va_list  pArguments;                 // Pointer To List Of Arguments

  if (string == NULL)                  // If There's No Text
    return;                            // Do Nothing

  va_start(pArguments, string);        // Parses The String For Variables
  vsprintf(text, string, pArguments);  // And Converts Symbols To Actual Numbers
  va_end(pArguments);                  // Results Are Stored In Text

  m_defaultFont->PrintText(x, y, text);
}

FontBase* OpenGLRenderer::AddTextureFont(const char* fontName, const char *textureFile)
{
  return NULL;
}

FontBase* OpenGLRenderer::AddSystemFont(STRING name, STRING fontName, int fontHeight)
{
  FontBase* font = new OpenGLFont();
  font->BuildSystemFont(fontName, fontHeight);

  m_fonts.Add(font);
  m_defaultFont = font;
  return font;
}

void OpenGLRenderer::SetClearColor(Color color)
{
  Renderer::SetClearColor(color);
  glClearColor(color.r, color.g, color.b, color.a);
}

void OpenGLRenderer::Clear(Color color)
{
  SetClearColor(color);
  glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}

Camera* OpenGLRenderer::CreateCamera()
{
  Camera* c = new OpenGLCamera();
  InsertCamera(EMPTY_STRING, c);
  return c;
}

Texture* OpenGLRenderer::CreateTexture(Texture::TexParam_t param, STRING name)
{
  Texture* t = new OpenGLTexture(param);
  InsertTexture(name, t);
  return t;
}

ShaderProgram* OpenGLRenderer::CreateShaderProgram()
{
  ShaderProgram* sm = new OpenGLShaderProgram();
  InsertShaderProgram(EMPTY_STRING, sm);

  return sm;
}

RenderTarget* OpenGLRenderer::CreateRenderTarget()
{
  RenderTarget* r = new OpenGLRenderTarget();
  InsertRenderTarget(EMPTY_STRING, r);
  return r;
}

void OpenGLRenderer::EnableTexture(bool enable)
{
  if(enable)
    glEnable(GL_TEXTURE_2D);
  else
    glDisable(GL_TEXTURE_2D);
}

void OpenGLRenderer::EnableDepthTest(bool enable)
{
  if(enable)
    glEnable(GL_DEPTH_TEST);
  else
    glDisable(GL_DEPTH_TEST);
}

void RayFrame::OpenGLRenderer::SetRenderMode( Renderer::RENDER_MODE rm )
{
  switch((int)rm)
  {
  case RENDER_POINT:
    glPolygonMode( GL_FRONT_AND_BACK, GL_POINT );
    break;
  case RENDER_FILL:
    glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
    break;
  case RENDER_LINE:
    glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
    break;
  }

}

void OpenGLRenderer::PushMatrix(MatrixType type)
{
  GLenum matType;
  switch (type)
  {
  case MATRIX_MODELVIEW:
    matType = GL_MODELVIEW;
    break;
  case MATRIX_PROJECTION:
    matType = GL_PROJECTION;
    break;
  case MATRIX_TEXTURECOORD:
    matType = GL_TEXTURE;
    break;
  default:
    assert(false);
  }

  glMatrixMode(matType);
  glPushMatrix();
}

void OpenGLRenderer::PopMatrix(MatrixType type)
{
  GLenum matType;
  switch (type)
  {
  case MATRIX_MODELVIEW:
    matType = GL_MODELVIEW;
    break;
  case MATRIX_PROJECTION:
    matType = GL_PROJECTION;
    break;
  case MATRIX_TEXTURECOORD:
    matType = GL_TEXTURE;
    break;
  default:
    assert(false);
  }

  glMatrixMode(matType);
  glPopMatrix();
}

void OpenGLRenderer::LoadMatrix(glm::mat4 m)
{
  glLoadMatrixf(value_ptr(m));
}

void OpenGLRenderer::SetColor(Color c)
{
  glColor4f(c.r, c.g, c.b, c.a);
}
